use std::{collections::HashMap, fs::File, io::Read};

use anyhow::Result;
use axum::{
    extract::{Path, Query},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Extension, Json, Router,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use tower::ServiceBuilder;
use tower_http::{
    compression::CompressionLayer, decompression::RequestDecompressionLayer, trace::TraceLayer,
};
use tracing::{info, level_filters::LevelFilter};

#[tokio::main]
async fn main() {
    //init tracing
    tracing_subscriber::fmt()
        .with_max_level(LevelFilter::TRACE)
        .with_thread_names(true)
        .init();

    let service = ServiceBuilder::new()
        // .layer(cors)
        .layer(Extension(State {}))
        .layer(RequestDecompressionLayer::new())
        .layer(CompressionLayer::new())
        .layer(TraceLayer::new_for_http());
    let app = Router::new()
        .route("/", get(hello))
        .route("/toml", get(toml_handle))
        .route("/error", get(error_handle))
        .route("/error2", get(error2_handle))
        .route("/users", post(create_user))
        .route("/user/filter", get(find_user))
        .route("/plain_text", get(|| async { "foo" }))
        .route("/json", get(|| async { Json(json!({"data":42})) }))
        .route("/user/{id}", get(get_user))
        .layer(service);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

    axum::serve(listener, app).await.unwrap();
}

async fn toml_handle() -> Result<String, AppError> {
    let mut file = File::open("Cargo.toml".to_string())?;
    let mut str = String::new();
    file.read_to_string(&mut str)?;
    Ok(str)
}

#[derive(Clone)]
struct State {}

async fn error_handle() -> Result<(), AppError> {
    try_thing()?;
    Ok(())
}

async fn error2_handle() -> Result<(), AppError> {
    Err(FormatError::MissingAttribute("abc".to_string()).into())
}

fn try_thing() -> Result<(), anyhow::Error> {
    anyhow::bail!("it failed!")
}

async fn hello() -> &'static str {
    info!("get /");
    "hello,world"
}

async fn create_user(Json(payload): Json<CreateUser>) -> (StatusCode, Json<User>) {
    info!("post /users,{:?}", payload);
    let user = User {
        id: 1,
        username: payload.username,
    };

    (StatusCode::OK, Json(user))
}

async fn get_user(Path(id): Path<u64>) -> (StatusCode, Json<User>) {
    info!("get /user/{}", id);
    let user = User {
        id,
        username: "abc".to_string(),
    };
    (StatusCode::OK, Json(user))
}

async fn find_user(Query(param): Query<HashMap<String, String>>) -> String {
    info!("/user/filter, {:?}", param);
    let r = param.iter().fold(String::new(), |acc, k| {
        if acc.is_empty() {
            acc + k.0 + ":" + k.1
        } else {
            acc + "," + k.0 + ":" + k.1
        }
    });
    r
}

#[derive(Error, Debug)]
pub enum FormatError {
    #[error("Invalid header (expected {expected:?}, got {found:?})")]
    InvalidHeader { expected: String, found: String },
    #[error("Missing attribute: {0}")]
    MissingAttribute(String),
}

#[derive(Deserialize, Debug)]
struct CreateUser {
    username: String,
}

#[derive(Serialize)]
struct User {
    id: u64,
    username: String,
}

struct AppError(anyhow::Error);

impl<E> From<E> for AppError
where
    E: Into<anyhow::Error>,
{
    fn from(value: E) -> Self {
        Self(value.into())
    }
}

impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Something went wrong:{}", self.0),
        )
            .into_response()
    }
}
